package ngat.oss.simulation;

import ngat.phase2.*;
import ngat.astrometry.*;
import ngat.util.*;
import ngat.util.logging.*;

import java.io.*;
import java.util.*;

/** Basic implementation of scoring model. Based on LT Dynamic Scheduler testbed 
 * -ngat.oss.TestScheduler. The objective weights are configured at runtime using
 * the configure(File) method.
 */
public class BasicScoringModel implements ScoringModel, PropertiesConfigurable {
  
    public static final double VERY_SMALL = Math.toRadians(0.1); // a very small angle (degs)
    
    Site site;

    public double wgtPriority;
    public double wgtLunar;
    public double wgtSeeing;
    public double wgtTrans;
    public double wgtHeight;
    public double wgtMiss;
    public double wgtAlloc;

    /** Accounting used/allocation fraction weight folding factor (p). The function drops to
     * 0.5 when x(=u/a) = p
     */ 
    public double pAlloc;

    /** Missed period scale factor - e-folding length in units of (period cycles).*/
    public double pMiss;
    
    Logger logger;

    /** Create a scoring model for a scope at the specified geographic Site.*/
    public BasicScoringModel(Site site) {
	this.site = site;
	logger = LogManager.getLogger("SIM");
    }


    /** Configure weights.
     * @see ngat.util.PropertiesConfigurable 
    */
    public void configure(ConfigurationProperties config) throws Exception {
	// defaults are typical 'sensible' values reflecting scoring
	// dominated by priority, trans/height and missed cycles.
	wgtPriority = config.getDoubleValue("wgt.priority" , 0.4);
	wgtLunar    = config.getDoubleValue("wgt.lunar",     0.25);
	wgtSeeing   = config.getDoubleValue("wgt.seeing",    0.25);
	wgtHeight   = config.getDoubleValue("wgt.height",    0.2);
	wgtTrans    = config.getDoubleValue("wgt.trans",     0.5);
	wgtMiss     = config.getDoubleValue("wgt.miss",      0.4);
	wgtAlloc    = config.getDoubleValue("wgt.alloc",     0.2);

	pAlloc      = config.getDoubleValue("scale.alloc",   5.0);
	pMiss       = config.getDoubleValue("scale.miss",    2.0);
    }
    
    /** Force the scoring weights to normailze.*/
    public void normalizeWeights() {

	double sum = wgtPriority + wgtLunar + wgtSeeing + wgtHeight + wgtTrans + wgtMiss + wgtAlloc ;
	
	wgtPriority /= sum; 
        wgtLunar    /= sum;
        wgtSeeing   /= sum;
        wgtHeight   /= sum;
        wgtTrans    /= sum;
        wgtMiss     /= sum;
        wgtAlloc    /= sum;

    }


    /** Calculate score for the group at time under conditions.	
     * @param group The group we want scored.
     * @param accounts The group's accounts.
     * @param time When we want the score calculated for.
     * @param seeing What is the seeing at that time 
     * (no assumptions made about where this came from).
     * @param lastExecution When was it last successfully executed (if ever, else 0L).
     */
    public double scoreGroup(Group group, 
			     Accounts accounts, 
			     long time, 
			     EnvironmentSnapshot env, 
			     ExecutionStatistics history) {
	
	double score = 0.0;
	
	int nobs = 0;
	Iterator io = group.listAllObservations();
	while (io.hasNext()) {
	    
	    Observation obs = (Observation)io.next();
	    
	    Source src = obs.getSource();
	    Position target = src.getPosition();
	    
	    double elev = target.getAltitude(time, site);	
	    double tth = target.getTransitHeight(site.getLatitude());
	    // div zero
	    if (Math.abs(tth) < VERY_SMALL)
		tth = VERY_SMALL;
	    // this is transit height NOT max height in current window 
	    // which is a better but massively more costly thing to work out.
	    //score += scoreHeight(elev);
	    score += scoreTransit(elev/tth);
	    
	    nobs++;
	    
	}

	score /= nobs;
	if (Double.isInfinite(score))
	    logger.log(1, "Inf value for score obs: "+group.getFullPath()+" Num.obs = "+nobs);
	
	// Using assigned group priorities.
	double score1 = 0.0;
	switch (group.getPriority()) {
	case -2:
	    // Make it very low so a normal never looses out.	    
	    score1 = wgtPriority*0.25;
	    break;
	case -1:
	    // For now we use p=3 which is High.
	    score1 = wgtPriority*3.0;	   
	    break;
	default:
	    score1 = wgtPriority*(double)group.getPriority();
	    break;
	}
	score += score1;
	
	// moon dark/bright
	int requestedLunar = group.getMinimumLunar();
	int actualLunar = Group.BRIGHT;
	Position moon = Astrometry.getLunarPosition(time);
	boolean moonup = moon.getAltitude(time, site) > Math.toRadians(-2.0);
	if (moonup) 
	    actualLunar = Group.BRIGHT;
	else	     
	    actualLunar = Group.DARK;

	double score_lun = wgtLunar/(1.0 + (actualLunar- requestedLunar));
	if (Double.isInfinite(score_lun))
            logger.log(1, "Inf value for score lun: "+group.getFullPath()+
		       " rl="+Group.toLunarString(requestedLunar)+
		       " l="+Group.toLunarString(actualLunar));
	
	score += score_lun;
	
	// seeing estimate.
	int requestedSeeing = group.getMinimumSeeing();
	double score_see = wgtSeeing/(1.0 + (env.seeing - requestedSeeing));
	if (Double.isInfinite(score_see)) 
	    logger.log(1, "Inf value for score see: "+group.getFullPath()+
		       " rs="+Group.toSeeingString(requestedSeeing)+
		       " s="+Group.toSeeingString(env.seeing));
	score += score_see;
	
	// missed window effect.
	if (group instanceof MonitorGroup) {
	    MonitorGroup mg = (MonitorGroup)group;
	    double mw = Math.max( (double)((time - history.lastExecuted)/mg.getPeriod())-1.0, 0.0);  ;
	    score += (1.0-Math.exp(-mw/pMiss))*wgtMiss;
	}

	// accounting effects. [no account -> no score boost]
	if (accounts != null) {
	    Account tot = accounts.getAccount(AccountingModel.ACCOUNT_TOTAL);
	    if (tot != null) {
		// this may be infinite or NaN
		double af = tot.getUsed() / tot.getAllocated();
		if (! Double.isNaN(af) && !Double.isInfinite(af)) {			
		    // The scale param (pAlloc) determines how fast the fn drops off.
		    double xa = af/pAlloc;
		    score += wgtAlloc / ( 1.0 + xa*xa);
		}
	    }
	}
	
	return score;
	
    }

    private double scoreHeight(double h) {
	double hh = Math.toDegrees(h);
	if (hh < 20.0)
	    return wgtHeight*hh/90.0;
	else
	    return wgtHeight*(1.0+(hh-20.0)/70.0);
    }
    
    private double scoreTransit(double t) {
	//	if (t < 0.3) 
	return wgtTrans*t;
	//else
	//  return wgtTrans*(0.4*(t-0.3)/0.7+0.6);
    }

}
